#include "Factory.h"

// hero
#include "HeroClass.h"

// sprites
#include "TestEnemy.h"
#include "Coin.h"
#include "Chest.h"
#include "Bomb.h"

// trigger
#include "LevelExitTrigger.h"
#include "PushLevelExitTrigger.h"
#include "TeleportTrigger.h"
#include "DoorExitTrigger.h"
#include "DoorTrigger.h"

#include "LevelList.h"
#include "LevelMap.h"
#include "Overlay.h"

enum ClassNames
{
    evNotDefined,
    evHero,
    evTestEnemy,
    evGeneric,
    evCoin,
    evChest,
    evBomb,
    evLevelExitTrigger,
    evPushLevelExitTrigger,
    evDoorExitTrigger,
    evTeleportTrigger,
    evDoorTrigger,
    evLevel,
	evLevelMap
};

// Map to associate the strings with the enum values
static std::map<std::string, ClassNames> s_mapClassNames;

Factory* Factory::m_factory = NULL;

Factory::Factory()
{
	/* preload the map to allow us to effectively switch on a string */
	if (s_mapClassNames.size() == 0)
	{
		s_mapClassNames["Hero"] = evHero;
		s_mapClassNames["TestEnemy"] = evTestEnemy;
		s_mapClassNames["Generic"] = evGeneric;
		s_mapClassNames["Coin"] = evCoin;
		s_mapClassNames["Chest"] = evChest;
		s_mapClassNames["Bomb"] = evBomb;
		s_mapClassNames["LevelExitTrigger"] = evLevelExitTrigger;
		s_mapClassNames["PushLevelExitTrigger"] = evPushLevelExitTrigger;
		s_mapClassNames["DoorExitTrigger"] = evDoorExitTrigger;
		s_mapClassNames["TeleportTrigger"] = evTeleportTrigger;
		s_mapClassNames["DoorTrigger"] = evDoorTrigger;
		s_mapClassNames["Level"] = evLevel;
		s_mapClassNames["LevelMap"] = evLevelMap;
	}
}

Factory::~Factory()
{

}

/* GetSingleton */
Factory* Factory::GetFactory()
{
	if (not m_factory)
        m_factory = new Factory();
	return m_factory;
}

/* Basically a thin wrapper around a class constructor */
BaseUnit* Factory::GetHero(Level *level, CRstring classname, const Vector2df p, const Vector2df v)
{
    BaseUnit *unit = NULL;
	switch (s_mapClassNames[classname]) {
		case evHero:
			unit = new Hero(p, v);
			break;
		default:
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
	}
	if (unit) unit->setLevel(level);
	return unit;
}

/* Parse a line directly from the level file, converting it into a constructor call */
BaseUnit* Factory::GetHero(Level *level, CRstring descriptor, int tileSize)
{
	vector<string> tokens;
	tokenize(descriptor, tokens, ",");
	return GetHero(level, tokens[0],Vector2df(atof(tokens[1].c_str())*tileSize,atof(tokens[2].c_str())*tileSize),Vector2df(atof(tokens[3].c_str()),atof(tokens[4].c_str())));
}

BaseUnit* Factory::GetSprite(Level *level, CRstring classname, const Vector2df p, const Vector2df v, CRstring parameter)
{
    if (parameter.size() == 0)
    {
        return GetSprite(level,classname,p,v);
    }
    BaseUnit *unit = NULL;
    switch (s_mapClassNames[classname])
    {
        default:
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
    }
    if (unit) unit->setLevel(level);
    return unit;
}

/* Basically a thin wrapper around a class constructor */
BaseUnit* Factory::GetSprite(Level *level, CRstring classname, const Vector2df p, const Vector2df v)
{
    BaseUnit *unit = NULL;
	switch (s_mapClassNames[classname]) {
		case evHero:
			unit = new Hero(p, v);
			break;
		case evTestEnemy:
			unit = new TestEnemy(p, v);
			break;
        case evCoin:
            unit = new Coin(p, v);
            break;
        case evChest:
            unit = new Chest(p, v);
            break;
        case evBomb:
            unit = new Bomb(p, v);
            break;
		default:
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
	}
	if (unit) unit->setLevel(level);
	return unit;
}

BaseUnit* Factory::GetGenericSprite(Level *level, CRstring classname, const Vector2df p, const Vector2df v, CRstring parameter)
{
    BaseUnit *unit = NULL;
    unit = new StatefulUnit(classname, p, v, parameter);
    if (unit) unit->setLevel(level);
    return unit;
}

/* Basically a thin wrapper around a class constructor */
BaseTrigger* Factory::GetTrigger(Level *level, CRstring classname, const Vector2df p, const Vector2df s)
{
    BaseTrigger* trigger = NULL;
	switch (s_mapClassNames[classname]) {
		case evLevelExitTrigger:
			trigger = new LevelExitTrigger(p, s);
			break;
        case evDoorExitTrigger:
            trigger = new DoorExitTrigger(p, s);
            break;
		default:
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
	}
	if (trigger) trigger->setLevel(level);
	return trigger;
}

/* Basically a thin wrapper around a class constructor */
BaseTrigger* Factory::GetTrigger(Level *level, CRstring classname, const Vector2df p, const Vector2df s, CRstring parameter)
{
    if (parameter.size() == 0)
    {
        return GetTrigger(level,classname,p,s);
    }
    BaseTrigger* trigger = NULL;
	switch (s_mapClassNames[classname]) {
		case evLevelExitTrigger:
			trigger = new LevelExitTrigger(p, s, parameter);
			break;
		case evPushLevelExitTrigger:
			trigger = new PushLevelExitTrigger(p, s, parameter);
			break;
        case evDoorExitTrigger:
            trigger = new DoorExitTrigger(p, s, parameter);
            break;
        case evTeleportTrigger:
            trigger = new TeleportTrigger(p, s, parameter);
            break;
        case evDoorTrigger:
            trigger = new DoorTrigger(p, s, parameter);
            break;
		default:
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
	}
	if (trigger) trigger->setLevel(level);
	return trigger;
}

//# Classname, identifier, imagefile, target level, x position, y position, width, height
Overlay* Factory::GetOverlay(LevelMap *lmp, CRstring classname, CRstring identifier, CRstring imagefile, CRstring target, const Vector2di p, const Vector2di s, uint transparentColour) {
    Overlay *o = NULL;
    if (classname == "LevelMarker") {
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
//        return new LevelMarker(identifier, imagefile, target, p, s);
    } else if (classname == "Overlay") {
        o = new Overlay(identifier, imagefile, target, p, s, transparentColour);
    } else {
        cout << "Factory: Missing information on how to construct " << classname << endl;
        exit(-1);
    }
    if (o) o->setLevelMap(lmp);
    return o;
}

Overlay* Factory::GetOverlay(LevelMap *lmp, CRstring classname, CRstring identifier, CRstring imagefile, const Vector2di p, const Vector2di s, uint transparentColour) {
    Overlay *o = NULL;
    if (classname == "LevelMarker") {
            cout << "Factory: Missing information on how to construct " << classname << endl;
            exit(-1);
    } else if (classname == "Overlay") {
        o = new Overlay(identifier, imagefile, p, s, transparentColour);
    } else {
        cout << "Factory: Missing information on how to construct " << classname << endl;
        exit(-1);
    }
    if (o) o->setLevelMap(lmp);
    return o;
}

/* Parse a line directly from the level file, converting it into a constructor call */
SpawnUnitData* Factory::GetSprite(CRstring descriptor, int tileSize)
{
	vector<string> tokens;
	tokenize(descriptor, tokens, ",",7);
    tokens.push_back(""); // make sure we have a parameter (although it might be empty)

	SpawnUnitData* data = NULL;
	if (tokens[0] == "Generic") {
        data = new SpawnUnitData(tokens[1],utGeneric,atof(tokens[2].c_str())*tileSize,atof(tokens[3].c_str())*tileSize,atof(tokens[4].c_str()),atof(tokens[5].c_str()),tokens[7],stringToBool(tokens[6].c_str()));
	} else {
        if (tokens.size() == 7)
            tokens[5] = tokens[5] + "," + tokens[6];
        data = new SpawnUnitData(tokens[0],utSprite,atof(tokens[1].c_str())*tileSize,atof(tokens[2].c_str())*tileSize,atof(tokens[3].c_str()),atof(tokens[4].c_str()),tokens[6],stringToBool(tokens[5].c_str()));
	}
	return data;
}

/* Parse a line directly from the level file, converting it into a constructor call */
SpawnUnitData* Factory::GetTrigger(CRstring descriptor, int tileSize)
{
	vector<string> tokens;
	tokenize(descriptor, tokens, ",",6);
    tokens.push_back(""); // make sure we have a parameter (although it might be empty)

	SpawnUnitData* data = NULL;
	data = new SpawnUnitData(tokens[0],utTrigger,atof(tokens[1].c_str())*tileSize,atof(tokens[2].c_str())*tileSize,atof(tokens[3].c_str())*tileSize,atof(tokens[4].c_str())*tileSize,tokens[5],true);
    return data;
}

/* Parse a line directly from the levelmap file, converting it into a constructor call */
Overlay* Factory::GetOverlay(LevelMap *lmp, CRstring descriptor, uint transparentColour)
{
    vector<string> tokens;
    tokenize(descriptor, tokens, ",", 8);
    if (tokens.size() == 7) {
        //# Classname, identifier, imagefile, x position, y position, width, height, x position, y position
        return GetOverlay(lmp, tokens[0], tokens[1], tokens[2], Vector2di(atoi(tokens[3].c_str()),atoi(tokens[4].c_str())), Vector2di(atoi(tokens[5].c_str()),atoi(tokens[6].c_str())), transparentColour);
    } else {
        //# Classname, identifier, imagefile, target level, x position, y position, width, height, x position, y position
        return GetOverlay(lmp, tokens[0], tokens[1], tokens[2], tokens[3], Vector2di(atoi(tokens[4].c_str()),atoi(tokens[5].c_str())), Vector2di(atoi(tokens[6].c_str()),atoi(tokens[7].c_str())), transparentColour);
    }
}

/* Parse a Level name, determine if it is a filename or a Classname, instantiate */
// descriptor = ClassName, [LevelFileName, Password]
// ClassName can either be Level or the name of a custom level class
WandorBaseState *Factory::GetLevel(CRstring descriptor)
{
	vector<string> tokens;
	MyGame *game = MyGame::GetMyGame();
    LevelData next;

	tokenize(descriptor, tokens, ",");
	if (tokens[0].find(".") != string::npos) { // descriptor is a level filename
		return new Level(tokens[0],game->GetPassword());
	} else {
		switch (s_mapClassNames[tokens[0]]) {
			case evLevel: // an ordinary level
				switch (tokens.size()) {
					case 1: // no further arguments - just get next level in (global) list
                        next = game->GetNextLevel(game->GetCurrentLevel()->GetFileName());
                        if (next.FileName == "") {
                            cout << "Critical error: Trying to get next level failed!" << endl;
                            exit(1);
                        } else {
                            return GetLevel(next);
                        }
//						return new Level(game->GetPassword());
						break;
					case 2: // descriptor has the filename of the next level (Level,level2.map)
						return new Level(tokens[1], game->GetPassword());
						break;
					case 3: // additionally a passowrd is passed
						return new Level(tokens[1], tokens[2]);
						break;
					default:
						break;
				}
            case evLevelMap:
                switch (tokens.size()) {
                    case 1:
                        break;
                    case 2:
                        return new LevelMap(tokens[1], game->GetPassword());
                        break;
                    case 3:
                        return new LevelMap(tokens[1], tokens[2]);
                    default:
                        break;
                }
			default:
				return NULL;
				break;
		}
	}
	return NULL;
}

/* instantiate Level from LevelData */
WandorBaseState *Factory::GetLevel(LevelData levelData)
{
	MyGame *game = MyGame::GetMyGame();

    switch (s_mapClassNames[levelData.LevelClass]) {
        case evLevel:
            return new Level(levelData.FileName, game->GetPassword());
            break;
        case evLevelMap:
            return new LevelMap(levelData.FileName, game->GetPassword());
            break;
        default:
            return NULL;
            break;
    }
	return NULL;
}
